{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Auto-Encoder Example\n",
    "\n",
    "Build a 2 layers auto-encoder with TensorFlow v2 to compress images to a lower latent space and then reconstruct them.\n",
    "\n",
    "- Author: Aymeric Damien\n",
    "- Project: https://github.com/aymericdamien/TensorFlow-Examples/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Auto-Encoder Overview\n",
    "\n",
    "<img src=\"http://kvfrans.com/content/images/2016/08/autoenc.jpg\" alt=\"ae\" style=\"width: 800px;\"/>\n",
    "\n",
    "References:\n",
    "- [Gradient-based learning applied to document recognition](http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf). Y. LeCun, L. Bottou, Y. Bengio, and P. Haffner. Proceedings of the IEEE, 86(11):2278-2324, November 1998.\n",
    "\n",
    "## MNIST Dataset Overview\n",
    "\n",
    "This example is using MNIST handwritten digits. The dataset contains 60,000 examples for training and 10,000 examples for testing. The digits have been size-normalized and centered in a fixed-size image (28x28 pixels) with values from 0 to 255. \n",
    "\n",
    "In this example, each image will be converted to float32, normalized to [0, 1] and flattened to a 1-D array of 784 features (28*28).\n",
    "\n",
    "![MNIST Dataset](http://neuralnetworksanddeeplearning.com/images/mnist_100_digits.png)\n",
    "\n",
    "More info: http://yann.lecun.com/exdb/mnist/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import absolute_import, division, print_function\n",
    "\n",
    "import tensorflow as tf\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# MNIST Dataset parameters.\n",
    "num_features = 784 # data features (img shape: 28*28).\n",
    "\n",
    "# Training parameters.\n",
    "learning_rate = 0.01\n",
    "training_steps = 20000\n",
    "batch_size = 256\n",
    "display_step = 1000\n",
    "\n",
    "# Network Parameters\n",
    "num_hidden_1 = 128 # 1st layer num features.\n",
    "num_hidden_2 = 64 # 2nd layer num features (the latent dim)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Prepare MNIST data.\n",
    "from tensorflow.keras.datasets import mnist\n",
    "(x_train, y_train), (x_test, y_test) = mnist.load_data()\n",
    "# Convert to float32.\n",
    "x_train, x_test = x_train.astype(np.float32), x_test.astype(np.float32)\n",
    "# Flatten images to 1-D vector of 784 features (28*28).\n",
    "x_train, x_test = x_train.reshape([-1, num_features]), x_test.reshape([-1, num_features])\n",
    "# Normalize images value from [0, 255] to [0, 1].\n",
    "x_train, x_test = x_train / 255., x_test / 255."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Use tf.data API to shuffle and batch data.\n",
    "train_data = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
    "train_data = train_data.repeat().shuffle(10000).batch(batch_size).prefetch(1)\n",
    "\n",
    "test_data = tf.data.Dataset.from_tensor_slices((x_test, y_test))\n",
    "test_data = test_data.repeat().batch(batch_size).prefetch(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Store layers weight & bias\n",
    "\n",
    "# A random value generator to initialize weights.\n",
    "random_normal = tf.initializers.RandomNormal()\n",
    "\n",
    "weights = {\n",
    "    'encoder_h1': tf.Variable(random_normal([num_features, num_hidden_1])),\n",
    "    'encoder_h2': tf.Variable(random_normal([num_hidden_1, num_hidden_2])),\n",
    "    'decoder_h1': tf.Variable(random_normal([num_hidden_2, num_hidden_1])),\n",
    "    'decoder_h2': tf.Variable(random_normal([num_hidden_1, num_features])),\n",
    "}\n",
    "biases = {\n",
    "    'encoder_b1': tf.Variable(random_normal([num_hidden_1])),\n",
    "    'encoder_b2': tf.Variable(random_normal([num_hidden_2])),\n",
    "    'decoder_b1': tf.Variable(random_normal([num_hidden_1])),\n",
    "    'decoder_b2': tf.Variable(random_normal([num_features])),\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Building the encoder.\n",
    "def encoder(x):\n",
    "    # Encoder Hidden layer with sigmoid activation.\n",
    "    layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['encoder_h1']),\n",
    "                                   biases['encoder_b1']))\n",
    "    # Encoder Hidden layer with sigmoid activation.\n",
    "    layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['encoder_h2']),\n",
    "                                   biases['encoder_b2']))\n",
    "    return layer_2\n",
    "\n",
    "\n",
    "# Building the decoder.\n",
    "def decoder(x):\n",
    "    # Decoder Hidden layer with sigmoid activation.\n",
    "    layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['decoder_h1']),\n",
    "                                   biases['decoder_b1']))\n",
    "    # Decoder Hidden layer with sigmoid activation.\n",
    "    layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['decoder_h2']),\n",
    "                                   biases['decoder_b2']))\n",
    "    return layer_2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Mean square loss between original images and reconstructed ones.\n",
    "def mean_square(reconstructed, original):\n",
    "    return tf.reduce_mean(tf.pow(original - reconstructed, 2))\n",
    "\n",
    "# Adam optimizer.\n",
    "optimizer = tf.optimizers.Adam(learning_rate=learning_rate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Optimization process. \n",
    "def run_optimization(x):\n",
    "    # Wrap computation inside a GradientTape for automatic differentiation.\n",
    "    with tf.GradientTape() as g:\n",
    "        reconstructed_image = decoder(encoder(x))\n",
    "        loss = mean_square(reconstructed_image, x)\n",
    "\n",
    "    # Variables to update, i.e. trainable variables.\n",
    "    trainable_variables = weights.values() + biases.values()\n",
    "    \n",
    "    # Compute gradients.\n",
    "    gradients = g.gradient(loss, trainable_variables)\n",
    "    \n",
    "    # Update W and b following gradients.\n",
    "    optimizer.apply_gradients(zip(gradients, trainable_variables))\n",
    "    \n",
    "    return loss"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "step: 0, loss: 0.234978\n",
      "step: 1000, loss: 0.014881\n",
      "step: 2000, loss: 0.010402\n",
      "step: 3000, loss: 0.008817\n",
      "step: 4000, loss: 0.007337\n",
      "step: 5000, loss: 0.006399\n",
      "step: 6000, loss: 0.006039\n",
      "step: 7000, loss: 0.005042\n",
      "step: 8000, loss: 0.005235\n",
      "step: 9000, loss: 0.004838\n",
      "step: 10000, loss: 0.004552\n",
      "step: 11000, loss: 0.004717\n",
      "step: 12000, loss: 0.004550\n",
      "step: 13000, loss: 0.004633\n",
      "step: 14000, loss: 0.004469\n",
      "step: 15000, loss: 0.004503\n",
      "step: 16000, loss: 0.003971\n",
      "step: 17000, loss: 0.004258\n",
      "step: 18000, loss: 0.004012\n",
      "step: 19000, loss: 0.003703\n",
      "step: 20000, loss: 0.003933\n"
     ]
    }
   ],
   "source": [
    "# Run training for the given number of steps.\n",
    "for step, (batch_x, _) in enumerate(train_data.take(training_steps + 1)):\n",
    "    \n",
    "    # Run the optimization.\n",
    "    loss = run_optimization(batch_x)\n",
    "    \n",
    "    if step % display_step == 0:\n",
    "        print(\"step: %i, loss: %f\" % (step, loss))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Testing and Visualization.\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Images\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAQUAAAD8CAYAAAB+fLH0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAIABJREFUeJztnXm8VXP3x99LKTI0UPQkMmVMKZSpkAwViVIUCWUuT6YKjzKVKeFJZCxTkilj0pOZKDSX0oMuKfWQRL/E9/fHOevuu/c9p3Pvmfa+567363Ve557v2WefdfY59/v9fNd3fdcS5xyGYRjKZmEbYBhGtLBOwTAMH9YpGIbhwzoFwzB8WKdgGIYP6xQMw/BhnYJhGD5y0imIyAkiskhElojIwFy8h2EYuUGyHbwkIlWAr4B2QBHwGXCGc25+Vt/IMIycUDUH5zwEWOKcWwogIuOBTkDSTkFELKzSMHLPKudc3VQH5WL60ABYVuJxUbzNh4j0FZEZIjIjBzYYhlGab8tyUC6UgiRoK6UEnHNjgDFgSsEwokQulEIR0LDE452AH3LwPoZh5IBcdAqfAXuKyK4iUg3oDkzKwfsYhpEDsj59cM5tFJFLgclAFeBR59y8bL+PYRi5IetLkmkZYT6FMnHllVcCsOWWWwJwwAEHANClSxffcaNHjwbg448/BuCJJ57Il4lGtJnpnDso1UEW0WgYhg9TChWAZ599FiitCFLx9ddfA3DssccC8N1332XXsJBo3LgxAAsXLgSgf//+ANx3332h2VRWttpqKwDuuOMOAC644AIAZs6cCUDXrl0B+PbbMq0elhdTCoZhlJ9cxCkYWSKVQtCRcvLkyQDstttuAJx00kkA7L777gD06NEDgGHDhuXO2Dxy4IEHAvD3338DUFRUFKY55aJ+/foA9OnTB/A+Q4sWLQDo2LEjAKNGjQrBuhimFAzD8GFKIYIcdFBs2te5c2df+7x5sZXdk08+GYBVq1YB8NtvvwFQrVo1AD755BMAmjZtCsB2222XY4vzS7NmzQBYt24dAC+++GKY5pSJunVjWw7Gjh0bsiWpMaVgGIaPCqkUdI6t87IffohFUa9fv56nnnoKgB9//BGAJUuWhGBhZui8UyS2jUQVwvHHHw/A8uXLE77uiiuuAGDffff1tb/22ms5sTPf7L///gBceumlQMWIv+jXrx8Ap5xyCgCHHHLIJo9v3bo1AJttFhuvZ82axXvvvZdDC0tjSsEwDB8VMk5h6dKlADRq1CjpMWvXrgW8UTZd1LN9++23AzBjRv52eu+yyy6A91n+97//bfL4WbNmAd6IqmicwrRp07JtYl5RhThhwgQAjj76aADefffd0GxKxV9//QV4qwzJUGUQPO7bb7+lW7dugBfLkAEWp2AYRvmpkD4F9SVo7P+CBQsA2GeffWjevDkARx11FACtWrUCYNmyWN6Xhg1L7ur22LhxIwA//fQT4M3rFY0GzKdSKGtU21VXXQV4kX7K9OnTffcVnauvvhrwrks+v4vy8vrrrwOeAkjF6tWrAW8lSVXirrvuyqeffgpAlSpVsm1mQipkpzB16lTfvfLmm28W/127dm3AW75S6XXwwQcnPOf69esB+OqrrwCvo6lTpw7ghQxHCQ10ufHGGwFvSXLlypUADBo0CIDff/89BOuyh04TdalWvyNdkowSbdq0AWCvvfYCvOlAsunDAw88AMBbb70FwJo1awA45phjALj22muLj73ooosAb8NbrrDpg2EYPiqkUigLP//8M1DauRZUF0FOO+00wFMac+bMAbyQ4yihI6cqBEVtjbIDrjzo6KvoFC9KqJoZP348ANtvv33C43Tq8/zzzwMwdOhQoLSa0+P69u1bHPikzu4tttgCgH//+98A/Pnnn1n5DIopBcMwfBSsUigv9erVA+D+++8HPAeRztdTLQfmk5deegmA4447ztc+btw4AK677rq825RLmjRp4nusI2aUqFo19q+UTCGoauvevTvghagnQ5XCsGHDGDFiBAA1atQAvM8/aVIsy2G2/V2mFAzD8GFKIc4ll1wCeBtX1CexaNGi0GwKosukhx12GADVq1cHvFHn5ptvBrxlrYqOLif37t0bgC+++AKAKVOmhGZTedFl03PPPRdIrRCCTJo0qXjre7KVs2xjSsEwDB+VXikcfvjhAAwc6K+DqxtY5s6dm3ebkqEe6+BW6CeffBKIZixFJmh4tsaKaByKxpREkWCwUsuWLTM6n4gUnzN47iFDhgBw1llnZfQeQUwpGIbho9Irhfbt2wOw+eabA14cg6ZHjwKaVEVDuJV33nkHgBtuuCHfJuUFTRKjm/YmTpwYpjmb5MILLwRSb3wqLyeddFKp9HN6r0oh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\n",
      "text/plain": [
       "<Figure size 288x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Reconstructed Images\n"
     ]
    },
    {
     "data": {
      "image/png": "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VO30hBRGvnyFYtBn9unTBwjt8KRSRHz/6L7U/bjuuusCMGbMGCDkzZx44okVv2amFAzDSNDslYJmcrU+j1uCq6lpTGxd0qQU5EtQE1jF3f/yl78knouaLevTdByQySmQQlBG5vz584GQmZmvOYyORXkaRxxxBFB7d6ga8KolXpwHUcw5kSK46qqrEs/1mWoQqya4+nmHDh0SY5eak3LQscyaNYurr7664PEVgikFwzASNHulIKuw3nrrJV6XElCbc1EK61EuZEXuu+8+IKyl33nnHSBk7mnscS5Gmo5JXvm77747l0MiSz5o0CAgqLg46qDj0LW95pprAGjbtm3ifbLSF1xwQeJzSpmboexR5YjoXKst3J///GcgRBOEomHyfwwePBiA/v37A+Fan3322Tk/Q6VayplSMAwjQbNVCrKSq6++OhBmXqFYdU3PfNrZa69Mq07F4WWVlJcgC6hj0hpdr3/99ddVb94q6/70008DGesuy//4448D8MorrwAhUpJP4Wy55ZZAiPULHa8yOt977z0gnK+G+Ika6kvSfSXFqfcri3LcuHFAyGTU5+oY9VyqSPs2dI1btWrF9ttvD8DDDz+8zLGUClMKhmEkaDpmskBkNeLGnrHFTNN6O0ZjVzxfY5WXXmvno446CoA//OEPQFBJyleYOHEixx13HFC7FX2l+OUvfwmEvBHnXG7fyfHHHw8Eqxr7RoQ89o899hhQ+9q+//77QMh7KORaN/R+ULak1JnGGucW5NtrorEvXboUCPUtpBSWW245tttuOyDsjyj3vWpKwTCMBM1OKcRWRTNznOWWrzV4GpEVind4tmrVCoDrrrsOgLXXXjvxfuUraB2/77775tauZ5xxBlA5haQxa09AzboCDz2UaUy+ZMmSxO/E+1Rk8Q8//HAgWFOhXaDa8/H111+X9iDqYIMNNgBq10tYa621gHDfxWOJ369rJdVX832KVFQqZ8aUgmEYCZqVUqipEuLZVDO23iNvsWLkaUZRBMXEpQSkFGRRFy5cCMDRRx8NwOuvvw4Ey3r66aez+eabA5XP1NRYldMvvv32W2bOnAlA586dAejVqxcQYvbKSNT75DMRUoPTp08HYMaMGYmfx1a5lLko2pcRZ8BKGcg/kg/9nq6hakDUROpBiq/cNTDqVQrOuVucc4udc9NqvNbWOfeEc25W9rFN9nXn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\n",
      "text/plain": [
       "<Figure size 288x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Encode and decode images from test set and visualize their reconstruction.\n",
    "n = 4\n",
    "canvas_orig = np.empty((28 * n, 28 * n))\n",
    "canvas_recon = np.empty((28 * n, 28 * n))\n",
    "for i, (batch_x, _) in enumerate(test_data.take(n)):\n",
    "    # Encode and decode the digit image.\n",
    "    reconstructed_images = decoder(encoder(batch_x))\n",
    "    # Display original images.\n",
    "    for j in range(n):\n",
    "        # Draw the generated digits.\n",
    "        img = batch_x[j].numpy().reshape([28, 28])\n",
    "        canvas_orig[i * 28:(i + 1) * 28, j * 28:(j + 1) * 28] = img\n",
    "    # Display reconstructed images.\n",
    "    for j in range(n):\n",
    "        # Draw the generated digits.\n",
    "        reconstr_img = reconstructed_images[j].numpy().reshape([28, 28])\n",
    "        canvas_recon[i * 28:(i + 1) * 28, j * 28:(j + 1) * 28] = reconstr_img\n",
    "\n",
    "print(\"Original Images\")     \n",
    "plt.figure(figsize=(n, n))\n",
    "plt.imshow(canvas_orig, origin=\"upper\", cmap=\"gray\")\n",
    "plt.show()\n",
    "\n",
    "print(\"Reconstructed Images\")\n",
    "plt.figure(figsize=(n, n))\n",
    "plt.imshow(canvas_recon, origin=\"upper\", cmap=\"gray\")\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
